package org.smartboot.sosa.core.rmi;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.smartboot.sosa.rpc.RpcRequest;
import org.smartboot.sosa.rpc.RpcResponse;
import org.smartboot.sosa.rpc.Session;
import org.springframework.beans.factory.FactoryBean;

public class RmiClientFactoryBean<T> implements FactoryBean<T> {
	private static final Logger LOGGER = LogManager.getLogger(RmiClientFactoryBean.class);
	private Class<T> remoteInterface;
	private String url;
	private RmiClient rmiClient;
	private T object;
	private int timeout;

	@SuppressWarnings("unchecked")
	@Override
	public T getObject() throws Exception {
		if (object != null) {
			return object;
		}

		object = (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { remoteInterface },
			new InvocationHandler() {

				@Override
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					RpcRequest req = new RpcRequest();
					req.setInterfaceClass(remoteInterface.getName());
					req.setMethod(method.getName());
					Class<?>[] types = method.getParameterTypes();
					if (!ArrayUtils.isEmpty(types)) {
						String[] paramClass = new String[types.length];
						for (int i = 0; i < types.length; i++) {
							paramClass[i] = types[i].getName();
						}
						req.setParamClassList(paramClass);
					}
					req.setParams(args);

					Session session = rmiClient.getSession(url);
					if (session.isInvalid()) {
						LOGGER.warn("session " + session + " is invalid,will reconnect");
						rmiClient.removeSession(url);
						session = rmiClient.getSession(url);
					}
					if (session.isInvalid()) {
						throw new IOException("session is invalid");
					}
					RpcResponse rmiResp = (timeout > 0 ? session.sendRpcRequest(req, timeout)
						: session.sendRpcRequest(req));
					if (StringUtils.isNotBlank(rmiResp.getException())) {
						throw new RuntimeException(rmiResp.getException());
					}
					return rmiResp.getReturnObject();
				}
			});
		return object;
	}

	@Override
	public Class<T> getObjectType() {
		return remoteInterface;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	/**
	 * Setter method for property <tt>remoteInterface</tt>.
	 *
	 * @param remoteInterface
	 *            value to be assigned to property remoteInterface
	 */
	public final void setRemoteInterface(Class<T> remoteInterface) {
		this.remoteInterface = remoteInterface;
	}

	/**
	 * Setter method for property <tt>url</tt>.
	 *
	 * @param url
	 *            value to be assigned to property url
	 */
	public final void setUrl(String url) {
		this.url = url;
	}

	/**
	 * Setter method for property <tt>rmiClient</tt>.
	 *
	 * @param rmiClient
	 *            value to be assigned to property rmiClient
	 */
	public final void setRmiClient(RmiClient rmiClient) {
		this.rmiClient = rmiClient;
	}

	/**
	 * Setter method for property <tt>timeout</tt>.
	 *
	 * @param timeout
	 *            value to be assigned to property timeout
	 */
	public final void setTimeout(int timeout) {
		this.timeout = timeout;
	}

}
